Model Basics
- Family of Models - represents a general relationship or pattern between variables in your data
- Fitted Model finding a model from the family that is closest to your data; it is “best” according to some criteria.
gf_point(data = sim1, y ~ x)

# Trying models
models <- tibble(a1 = runif(250, -20, 50),
a2 = runif(250, -5,5))
gf_point(y ~x, data = sim1) %>%
gf_abline(intercept = ~a1, slope = ~a2, data = models,alpha = 0.3)

We can try to see how good any particular model is by the following:
# Choosing:
slope <- 2.5
intercept <- 1.5
# Add predictions with this model
sim1 <- sim1 %>% mutate(pred_y = intercept + slope * x)
# Plot distances
gf_jitter(y ~ x,width = 0.1, data = sim1) %>%
gf_abline(
intercept = ~ intercept,
slope = ~ slope,
color = "blue", data = sim1) %>%
gf_point(pred_y ~ x, data = sim1,color = "red") %>%
gf_segment(pred_y + y ~ x + x, data = sim1)

# Compute distances
model1 <- function(a,data){
a[1] + a[2]* data$x
}
model1(c(intercept, slope), sim1)
[1] 4.0 4.0 4.0 6.5 6.5 6.5 9.0 9.0 9.0 11.5 11.5 11.5 14.0
[14] 14.0 14.0 16.5 16.5 16.5 19.0 19.0 19.0 21.5 21.5 21.5 24.0 24.0
[27] 24.0 26.5 26.5 26.5
# Distance measure of the MODEL
measure_distance <- function(mod,data){
diff <- data$y - model1(mod, data)
sqrt(mean(diff^2))
}
measure_distance(c(intercept,slope), sim1)
[1] 2.501
# using `purrr` to compute distance measures for all 250 models
# we need to fix the distance computation into a helper function specifically for our data.
# `measure_distance` also has a data frame to be passed as a parameter, which we cannot do with our `purrr` command.
sim1_dist <- function(a1,a2){
measure_distance(c(a1,a2),sim1)
}
models <- models %>%
mutate(dist = purrr::map2_dbl(a1,a2,sim1_dist))
models
Now we can plot the 10 best models by ranking the models by the dist parameter.
gf_point(y~x, color = "grey30",data = sim1) %>%
gf_abline(intercept = ~ a1,slope = ~ a2, color = ~ dist, data = filter(models, rank(dist) <= 10))

We can also see a scatter plot of the parameters a1 and a2
gf_point( a1~ a2, data = filter(models, rank(dist)<=10), size = 4, color = "red") %>%
gf_point(a1 ~ a2, color = ~ -dist,data = models)

There could be a systematic search for models, by using a grid of points in the model space:
grid <- expand.grid(a1 = seq(-5, 10, length = 25),
a2 = seq(1.5,2.5,length = 25)) %>%
mutate(dist = purrr::map2_dbl(a1, a2, sim1_dist))
gf_point(a2 ~a1, data = filter(grid, rank(dist) <=10), color = "red", size = 4) %>%
gf_point(a2~a1, color = ~-dist, data = grid)

Aside: The Relationship between plotting the model parameter space and plotting models on the variable space is like the relationship between the Mandelbrot fractal and the Julia fractals.
Overlaying these 10 best models on the original data:
gf_point( y ~ x, data = sim1) %>%
gf_abline(intercept = ~a1, slope = ~ a2, color = ~ dist, data = filter(grid, rank(dist)<=10))

Rather than search through ever finer grids, we can use optimization in R to find the “best” model parameters a1, and a2.
best <- optim(par = c(0,0),fn = measure_distance, data = sim1)
best
$par
[1] 4.222 2.051
$value
[1] 2.128
$counts
function gradient
77 NA
$convergence
[1] 0
$message
NULL
sim_mod <- lm(y~x, data = sim1)
sim_mod
Call:
lm(formula = y ~ x, data = sim1)
Coefficients:
(Intercept) x
4.22 2.05
Adding Predictions
We can add a grid of data points to cover exactly the range of the variables we have in our dataset.
grid <- modelr::data_grid(data = sim1, x)
grid
# Add Predictions
grid <- grid %>% add_predictions(model = sim_mod, var = "pred")
grid
# Plot this
gf_point(y~x, data = sim1) %>%
gf_line(pred~x,data = grid,color = "red", size = 2)

We can add residuals to the original data frame using modelr
sim1 <- sim1 %>% add_residuals(model = sim_mod)
sim1
# Plotting the residuals
gf_point(data = sim1, resid~x) %>% gf_hline(yintercept = ~ 0)

gf_density(data = sim1, ~resid)

Frequency spread of the residuals looks reasonable. No regular pattern “left over” in the residuals.
Model Families
The model_matrix() function: >It takes a data frame and a formula and returns a tibble that defines the model equation: each column in the output is associated with one coefficient in the model, the function is always y = a_1 * out1 + a_2 * out_2.
Categorical Variables
We use the sim2 dataset to explore this.
sim2 %>% gf_point(y~x)

# Model fitting
mod2 <- lm(y~x, data = sim2)
grid <- sim2 %>% data_grid(x) %>% add_predictions(mod2)
grid
# Plot the model on the data
gf_point(y~x, data = sim2) %>%
gf_point(pred~x,data = grid, color = "red",size = 4)

With a categorical variable for x, we predict the mean value for each category with our model.
Interaction: Continuous and Categorical variable
sim3
gf_point(y~x1, data = sim3, color = ~x2)

We can fit two kinds of models: with and without interactions
mod1 = lm(y ~ x1 + x2, data = sim3)
mod2 = lm(y ~ x1 * x2, data = sim3)
# Use data_grid
grid <- sim3 %>% data_grid(x1,x2) %>% gather_predictions(mod1,mod2)
grid
sim3 %>%
gf_point(y ~ x1, color = ~x2, data = sim3) %>%
gf_line(pred ~ x1 | model, data = grid)

Recall the discussion in Chester Ismay’s Modern Dive. mod1 uses the same slope for both models and only varies the intercept, whereas mod2 varies both model parameters.
We can check which model is better by plotting residuals.
sim3 %>% gather_residuals(mod1, mod2) %>%
gf_point(resid ~ x1|model ~ x2, color = ~x2, data = sim3)

mod2 residuals have no pattern and look random. mod1 residuals do have patterns, especially for category b.
Interaction: Two Continuous Variables
As in the last section we can explore two kinds of models, with and wthout interaction.
sim4
mod1 <- lm( y ~ x1 + x2, data = sim4)
mod2 <- lm( y ~ x1 * x2, data = sim4)
# Visualisation
grid <- sim4 %>%
data_grid(x1 = seq_range(x1,n = 5,pretty = TRUE),
x2 = seq_range(x2, 5,pretty = TRUE)) %>%
gather_predictions(mod1, mod2)
# seq_range is a modelr command, ot generate n numbers spaced over the range of a variable.
grid
# Visualisation
gf_tile(x2 ~ x1 | model, fill = ~ pred, data = grid )

Can’t see much difference there….
gf_line(pred ~ x1 | model, color = ~ x2, group = ~ x2, data = grid) %>% gf_refine(scale_color_distiller(palette = 7))

gf_line(pred ~ x2 |~ model, color = ~ x1, group = ~ x1, data = grid)

Let’s look at the residuals…
sim4 %>%
gather_residuals(mod1, mod2) %>%
gf_point(resid ~ x1|model ~ x2, color = ~x2, data = sim4)

# What plot can we use to show which model is better?
sim4 %>%
gather_residuals(mod1, mod2) %>%
gf_point(mean(abs(resid))~ x1 | model ~ x2, color = ~ x2, data = .)

# NEEDS MORE IMAGINATION AND WORK!!
Transformation while Modelling
Data variables can also be algebraically transformed while putting them into the model. Actual arithmetic operators like + and * should be wrapped in I() to ensure that they are interpreted correctly. It is always good to check with model_matrix what the model is doing, so that we know what we are getting. > Note we can do the modelling itself inside the model_matrix command!
df <- tribble(~y, ~x, 1,1,2,2,3,3)
model_matrix(df, y ~ x^2 + x)
# This uses the Wilkinson-Rogers Notation !!
model_matrix(df, y ~ I(x^2) + x)
# Can also use I(x^2 + x)
There are many transformations possible Using Taylor’s series is one way
model_matrix(df, y ~ poly(x, 2))
poly fits the data well within the range; outside, when it is extrapolating, it may may shoot off to infinity. In this case it is better to use natural splines, which is somewhat better, though it still makes errors outside the data range.
library(splines)
model_matrix(df, y ~ ns(x, 2))
Let us model nonlinear data.
sim5 <- tibble(x = seq(0, 3.5 * pi, length = 50),
y = 4 * sin(x) + rnorm(length(x)))
gf_point(y ~ x, data = sim5)

# We can fit multiple models to this data
mod1 <- lm(y ~ ns(x, 1), data = sim5)
mod2 <- lm(y ~ ns(x, 2), data = sim5)
mod3 <- lm(y ~ ns(x, 3), data = sim5)
mod4 <- lm(y ~ ns(x, 4), data = sim5)
mod5 <- lm(y ~ ns(x, 5), data = sim5)
grid <- sim5 %>%
data_grid(x = seq_range(x,n = 50,expand = 0.1)) %>%
gather_predictions(mod1,mod2,mod3,mod4,mod5, .pred = "y")
# Plotting the 5 models
gf_point(data = sim5, y ~ x) %>%
gf_line(data = grid, y ~ x | model, color = ~model)

NA
Notice that the extrapolation outside the range of the data is clearly bad. This is the downside to approximating a function with a polynomial. But this is a very real problem with every model: the model can never tell you if the behaviour is true when you start extrapolating outside the range of the data that you have seen. You must rely on theory and science.
Other Model Families
** Generalised Linear models:** stats::glm() extend linear models to count or binary response variables. Use a different metric for distance.
** Generalised Additive Models:** mgcv::gam() can use arbitrary smooth modelling functions like y ~ s(x). gam() will estimate the function. (Rather like the regression software I used earlier.)
** Penalised Linear Models:** glmnet::glmnet() Adds a penalty vector in parameter space corresponding to distance of that vector from the origin. Tends to make models generalise better to new datasets from the population.
** Robust Linear Models:** MASS::rlm() tweaks the distance metric to down-weight data points that are far away, i.e. outliers. Less sensitive to outliers, but not so good when there are no outliers.
** Trees:** Fit piece-wise linear models to smaller and smaller pieces of data ( like a Lindenmayer fractal). Work best when used in aggregate models like randomForest::randomForest() and gradient boost xgboost::xgboost().
A Generalized Linear Model (GLM/GLZ) helps represent the dependent variable as a linear combination of independent variables. In its simplest form, a linear model specifies the (linear) relationship between a dependent (or response) variable Y, and a set of predictor variables, the X’s, so that
\[
Y = b_0 + b_1X_1 + b_2X_2 + ... + b_kX_k + e
\]
In the GLZs, the model is assumed to be: \[
Y = g (b_0 + b_1X_1 + b_2X_2 + ... + b_kX_k )+ e
\] The inverse of the funtion g(...), say f(…)is called thelink function`, so that:
\[
f(\mu_Y)= b_0 + b_1X_1 + b_2X_2 + ... + b_kX_k
\]
GLZs work when :
the dependent variable has a discrete/multinomial distribution. The distribution of the dependent or response variable can be (explicitly) non-normal, and does not have to be continuous, i.e., it can be binomial, multinomial, or ordinal multinomial (i.e., contain information on ranks only);
the relationship between dependent and independent variable (i.e. the link function) is inherently nonlinear, or a power relationship, for example.
Types of link functions and distributions of y dependent variables
Various link functions can be chosen based on the assumbned distributions of the y dependent variable:

CocaCola Sales Data Exploration
# Temperature vs CocaCola sales
cola <- read_csv("./cola.csv")
Parsed with column specification:
cols(
Temperature = [32mcol_double()[39m,
Cola = [32mcol_double()[39m
)
penalty <- read_csv("./penalty.csv")
Parsed with column specification:
cols(
Practice = [32mcol_double()[39m,
Outcome = [32mcol_double()[39m
)
str(cola)
Classes ‘spec_tbl_df’, ‘tbl_df’, ‘tbl’ and 'data.frame': 48 obs. of 2 variables:
$ Temperature: num 1 2 3 4 5 6 7 8 9 10 ...
$ Cola : num 1 1 1 1 1 1 1 1 2 2 ...
- attr(*, "spec")=
.. cols(
.. Temperature = [32mcol_double()[39m,
.. Cola = [32mcol_double()[39m
.. )
gf_point(Cola ~ Temperature, data = cola)

Linear Model Fitting
model =lm(data = cola, Cola ~ Temperature)
gf_point(Cola ~ Temperature, data = cola) %>%
gf_abline(intercept = ~model$coefficients[1],
slope = ~model$coefficients[2],data = cola)

#Calculate RMSE
PredCola <- predict(model, cola)
RMSE <- modelr::rmse(model, cola)
##
model
Call:
lm(formula = Cola ~ Temperature, data = cola)
Coefficients:
(Intercept) Temperature
-278 20
RMSE
[1] 241.5
The linear model is clearly inadequate and makes faulty predictions. We try to use a log-linear model next, since the Cola sales figure seems to have an exponential relationship with Temperature. We see that: \[Cola = a * b ^ {Temperature} ....Eqn(1)\]
Such growth models depict a variety of real life situations and can be modeled using log-linear regression. Apart from exponential relationship, log transformation on (the) dependent variable is also used when dependent variable follows: a) log-normal distribution - log-normal distribution is distribution of a random variable whose log follows normal distribution. Thus, taking log of a log-normal random variable makes the variable normally distributed and fit for linear regression.
b) Poisson distribution - Poisson distribution is the distribution of random variable that results from a Poisson experiment. For example, the number of successes or failures in a time period T follows Poisson distribution.
Taking log on both sides of Eqn.1:
\[log(Cola) = log(a) + Temperature * log(b)\]
Log Model Fitting
We transform the data using log and then fit a linear model to the log-transformed variables
cola <- cola %>% mutate(logCola = log(Cola))
logmodel <- lm(logCola ~ Temperature, data = cola)
logmodel
Call:
lm(formula = logCola ~ Temperature, data = cola)
Coefficients:
(Intercept) Temperature
-0.909 0.172
# Plots
gf_point(data = cola, logCola~Temperature) %>%
gf_abline(intercept = ~logmodel$coefficients[1],slope = ~logmodel$coefficients[2])

# Predictions and RMSE
PredLogCola <- predict(logmodel,cola)
RMSElog <- modelr::rmse(logmodel, cola)
PredLogCola
1 2 3 4 5 6 7
-0.73672 -0.56445 -0.39217 -0.21989 -0.04762 0.12466 0.29694
8 9 10 11 12 13 14
0.46921 0.64149 0.81377 0.98605 1.15832 1.33060 1.50288
15 16 17 18 19 20 21
1.67515 1.84743 2.01971 2.19198 2.36426 2.53654 2.70881
22 23 24 25 26 27 28
2.88109 3.05337 3.22564 3.39792 3.57020 3.74248 3.91475
29 30 31 32 33 34 35
4.08703 4.25931 4.43158 4.60386 4.77614 4.94841 5.12069
36 37 38 39 40 41 42
5.29297 5.46524 5.63752 5.80980 5.98207 6.15435 6.32663
43 44 45 46 47 48
6.49891 6.67118 6.84346 7.01574 7.18801 7.36029
RMSElog
[1] 0.2466
Hence the log-linear model is:
\[ log(Cola_i) = -0.909 + 0.172 * log(Temperature_i) \]
str(PredLogCola)
Named num [1:48] -0.7367 -0.5644 -0.3922 -0.2199 -0.0476 ...
- attr(*, "names")= chr [1:48] "1" "2" "3" "4" ...
gf_col(data = PredLogCola,value ~ row_number(PredLogCola))
Error: `data` must be a data frame, or other object coercible by `fortify()`, not a numeric vector
LS0tCnRpdGxlOiAiR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVscyIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgZmlnX2NhcHRpb246IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBjZXJ1bGVhbgogICAgdG9jOiB5ZXMKICBnaXRodWJfZG9jdW1lbnQ6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OgogICAgbGF0ZXhfZW5naW5lOiB4ZWxhdGV4Ci0tLQpgYGB7ciBzZXQgdXAsIG1lc3NhZ2U9RkFMU0UsZWNobz1GQUxTRSxjYWNoZT1UUlVFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShtb2RlbHIpCmxpYnJhcnkoZ2dmb3JtdWxhKQpsaWJyYXJ5KHBsYW50dW1sKSAjIGZvciBkb2N1bWVudGF0aW9uCmxpYnJhcnkoRGlhZ3JhbW1lUikgIyBmb3IgZG9jdW1lbnRhdGlvbgojIHVwZGF0ZVBsYW50dW1sSmFyKCkKYGBgCgoKIyBJbnRyb2R1Y3Rpb24KRm9sbG93aW5nOiAgCjEuIFdpY2toYW0gLSBHcm9sZW11bmQgb24gW01vZGVsc10oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9tb2RlbC1iYXNpY3MuaHRtbCkKCjIuPGh0dHBzOi8vd3d3LmtkbnVnZ2V0cy5jb20vMjAxNy8xMC9sZWFybi1nZW5lcmFsaXplZC1saW5lYXItbW9kZWxzLWdsbS1yLmh0bWw+ICAKYW5kIHRoZXJlYWZ0ZXI6CgozLjxodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS9ob3ctdG8tcGVyZm9ybS1vcmRpbmFsLWxvZ2lzdGljLXJlZ3Jlc3Npb24taW4tci8+ICAKCiMgTW9kZWwgQmFzaWNzCiAtICoqRmFtaWx5IG9mIE1vZGVscyoqIC0gcmVwcmVzZW50cyBhIGdlbmVyYWwgcmVsYXRpb25zaGlwIG9yIHBhdHRlcm4gYmV0d2VlbiB2YXJpYWJsZXMgaW4geW91ciBkYXRhCiAtICoqRml0dGVkIE1vZGVsKiogZmluZGluZyBhIG1vZGVsIGZyb20gdGhlIGZhbWlseSB0aGF0IGlzIGNsb3Nlc3QgdG8geW91ciBkYXRhOyBpdCBpcyAiYmVzdCIgYWNjb3JkaW5nIHRvIHNvbWUgY3JpdGVyaWEuIAoKYGBge3Igc2ltMX0KZ2ZfcG9pbnQoZGF0YSA9IHNpbTEsIHkgfiB4KQoKIyBUcnlpbmcgbW9kZWxzCm1vZGVscyA8LSB0aWJibGUoYTEgPSBydW5pZigyNTAsIC0yMCwgNTApLAogICAgICAgICAgICAgICAgIGEyID0gcnVuaWYoMjUwLCAtNSw1KSkKZ2ZfcG9pbnQoeSB+eCwgZGF0YSA9IHNpbTEpICU+JSAKICBnZl9hYmxpbmUoaW50ZXJjZXB0ID0gfmExLCBzbG9wZSA9IH5hMiwgZGF0YSA9IG1vZGVscyxhbHBoYSA9IDAuMykKYGBgCgpXZSBjYW4gdHJ5IHRvIHNlZSBob3cgZ29vZCBhbnkgcGFydGljdWxhciBtb2RlbCBpcyBieSB0aGUgZm9sbG93aW5nOgoKYGBge3Igc2VlaW5nIGRpc3RhbmNlc30KIyBDaG9vc2luZzoKc2xvcGUgPC0gMi41CmludGVyY2VwdCA8LSAxLjUKIyBBZGQgcHJlZGljdGlvbnMgd2l0aCB0aGlzIG1vZGVsCnNpbTEgPC0gc2ltMSAlPiUgbXV0YXRlKHByZWRfeSA9IGludGVyY2VwdCArIHNsb3BlICogeCkKIyBQbG90IGRpc3RhbmNlcwpnZl9qaXR0ZXIoeSB+IHgsd2lkdGggPSAwLjEsIGRhdGEgPSBzaW0xKSAlPiUKICBnZl9hYmxpbmUoCiAgICBpbnRlcmNlcHQgPSB+IGludGVyY2VwdCwKICAgIHNsb3BlID0gfiBzbG9wZSwKICAgIGNvbG9yID0gImJsdWUiLCBkYXRhID0gc2ltMSkgJT4lIAogIGdmX3BvaW50KHByZWRfeSB+IHgsIGRhdGEgPSBzaW0xLGNvbG9yID0gInJlZCIpICU+JQogIGdmX3NlZ21lbnQocHJlZF95ICsgeSB+IHggKyB4LCBkYXRhID0gc2ltMSkKYGBgCgoKYGBge3IgY29tcHV0ZSBkaXN0YW5jZXN9CiMgQ29tcHV0ZSBkaXN0YW5jZXMKbW9kZWwxIDwtIGZ1bmN0aW9uKGEsZGF0YSl7CiAgYVsxXSArIGFbMl0qIGRhdGEkeAp9Cgptb2RlbDEoYyhpbnRlcmNlcHQsIHNsb3BlKSwgc2ltMSkKCiMgRGlzdGFuY2UgbWVhc3VyZSBvZiB0aGUgTU9ERUwKbWVhc3VyZV9kaXN0YW5jZSA8LSBmdW5jdGlvbihtb2QsZGF0YSl7CiAgZGlmZiA8LSBkYXRhJHkgLSBtb2RlbDEobW9kLCBkYXRhKQogIHNxcnQobWVhbihkaWZmXjIpKQp9Cm1lYXN1cmVfZGlzdGFuY2UoYyhpbnRlcmNlcHQsc2xvcGUpLCBzaW0xKQoKCiMgdXNpbmcgYHB1cnJyYCB0byBjb21wdXRlIGRpc3RhbmNlIG1lYXN1cmVzIGZvciBhbGwgMjUwIG1vZGVscwojIHdlIG5lZWQgdG8gZml4IHRoZSBkaXN0YW5jZSBjb21wdXRhdGlvbiBpbnRvIGEgaGVscGVyIGZ1bmN0aW9uIHNwZWNpZmljYWxseSBmb3Igb3VyIGRhdGEuCiMgYG1lYXN1cmVfZGlzdGFuY2VgIGFsc28gaGFzIGEgZGF0YSBmcmFtZSB0byBiZSBwYXNzZWQgYXMgYSBwYXJhbWV0ZXIsIHdoaWNoIHdlIGNhbm5vdCBkbyB3aXRoIG91ciBgcHVycnJgIGNvbW1hbmQuCnNpbTFfZGlzdCA8LSBmdW5jdGlvbihhMSxhMil7CiAgbWVhc3VyZV9kaXN0YW5jZShjKGExLGEyKSxzaW0xKQp9Cgptb2RlbHMgPC0gbW9kZWxzICU+JSAKICBtdXRhdGUoZGlzdCA9IHB1cnJyOjptYXAyX2RibChhMSxhMixzaW0xX2Rpc3QpKQptb2RlbHMKYGBgCgoKTm93IHdlIGNhbiBwbG90IHRoZSAxMCBiZXN0IG1vZGVscyBieSByYW5raW5nIHRoZSBgbW9kZWxzYCBieSB0aGUgYGRpc3RgIHBhcmFtZXRlci4gCgpgYGB7ciBwbG90dGluZyB0aGUgMTAgYmVzdH0KZ2ZfcG9pbnQoeX54LCBjb2xvciA9ICJncmV5MzAiLGRhdGEgPSBzaW0xKSAlPiUgCiAgZ2ZfYWJsaW5lKGludGVyY2VwdCA9IH4gYTEsc2xvcGUgPSB+IGEyLCBjb2xvciA9IH4gZGlzdCwgZGF0YSA9IGZpbHRlcihtb2RlbHMsIHJhbmsoZGlzdCkgPD0gMTApKQpgYGAKCldlIGNhbiBhbHNvIHNlZSBhIHNjYXR0ZXIgcGxvdCBvZiB0aGUgKipwYXJhbWV0ZXJzKiogYGExYCBhbmQgYGEyYApgYGB7ciBwbG90dGluZyB0aGUgcGFyYW1ldGVyc30KZ2ZfcG9pbnQoIGExfiBhMiwgZGF0YSA9IGZpbHRlcihtb2RlbHMsIHJhbmsoZGlzdCk8PTEwKSwgc2l6ZSA9IDQsIGNvbG9yID0gInJlZCIpICU+JSAKICBnZl9wb2ludChhMSB+IGEyLCBjb2xvciA9IH4gLWRpc3QsZGF0YSA9IG1vZGVscykKYGBgCgpUaGVyZSBjb3VsZCBiZSBhIHN5c3RlbWF0aWMgc2VhcmNoIGZvciBtb2RlbHMsIGJ5IHVzaW5nIGEgZ3JpZCBvZiBwb2ludHMgaW4gdGhlIGBtb2RlbCBzcGFjZWA6CmBgYHtyIEV4cGxvcmluZyB0aGUgTW9kZWwgU3BhY2V9CmdyaWQgPC0gZXhwYW5kLmdyaWQoYTEgPSBzZXEoLTUsIDEwLCBsZW5ndGggPSAyNSksCiAgICAgICAgICAgICAgICAgICAgYTIgPSBzZXEoMS41LDIuNSxsZW5ndGggPSAyNSkpICU+JSAKICBtdXRhdGUoZGlzdCA9IHB1cnJyOjptYXAyX2RibChhMSwgYTIsIHNpbTFfZGlzdCkpCgpnZl9wb2ludChhMiB+YTEsIGRhdGEgPSBmaWx0ZXIoZ3JpZCwgcmFuayhkaXN0KSA8PTEwKSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDQpICU+JSAKICBnZl9wb2ludChhMn5hMSwgY29sb3IgPSB+LWRpc3QsIGRhdGEgPSBncmlkKQpgYGAKCkFzaWRlOiBUaGUgUmVsYXRpb25zaGlwIGJldHdlZW4gcGxvdHRpbmcgdGhlIG1vZGVsIHBhcmFtZXRlciBzcGFjZSBhbmQgcGxvdHRpbmcgbW9kZWxzIG9uIHRoZSB2YXJpYWJsZSBzcGFjZSBpcyBsaWtlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgTWFuZGVsYnJvdCBmcmFjdGFsIGFuZCB0aGUgSnVsaWEgZnJhY3RhbHMuIAoKT3ZlcmxheWluZyB0aGVzZSAxMCBiZXN0IG1vZGVscyBvbiB0aGUgb3JpZ2luYWwgZGF0YToKCmBgYHtyIFBsb3R0aW5nIHRoZSAxMCBiZXN0fQpnZl9wb2ludCggeSB+IHgsIGRhdGEgPSBzaW0xKSAlPiUgCiAgZ2ZfYWJsaW5lKGludGVyY2VwdCA9IH5hMSwgc2xvcGUgPSB+IGEyLCBjb2xvciA9IH4gZGlzdCwgZGF0YSA9IGZpbHRlcihncmlkLCByYW5rKGRpc3QpPD0xMCkpCmBgYAoKUmF0aGVyIHRoYW4gc2VhcmNoIHRocm91Z2ggZXZlciBmaW5lciBncmlkcywgd2UgY2FuIHVzZSBvcHRpbWl6YXRpb24gaW4gUiB0byBmaW5kIHRoZSAiYmVzdCIgbW9kZWwgcGFyYW1ldGVycyBgYTFgLCBhbmQgYGEyYC4KCmBgYHtyIE9wdGltdW0gTW9kZWxzfQpiZXN0IDwtIG9wdGltKHBhciA9IGMoMCwwKSxmbiA9IG1lYXN1cmVfZGlzdGFuY2UsIGRhdGEgPSBzaW0xKQpiZXN0CnNpbV9tb2QgPC0gbG0oeX54LCBkYXRhID0gc2ltMSkKc2ltX21vZApgYGAKCiMjIEFkZGluZyBQcmVkaWN0aW9ucwoKV2UgY2FuIGFkZCBhIGBncmlkYCBvZiBkYXRhIHBvaW50cyB0byBjb3ZlciBleGFjdGx5IHRoZSByYW5nZSBvZiB0aGUgdmFyaWFibGVzIHdlIGhhdmUgaW4gb3VyIGRhdGFzZXQuIAoKYGBge3IgQWRkIFByZWRpY3Rpb25zfQpncmlkIDwtIG1vZGVscjo6ZGF0YV9ncmlkKGRhdGEgPSBzaW0xLCB4KQpncmlkCgojIEFkZCBQcmVkaWN0aW9ucwpncmlkIDwtIGdyaWQgJT4lIGFkZF9wcmVkaWN0aW9ucyhtb2RlbCA9IHNpbV9tb2QsIHZhciA9ICJwcmVkIikKZ3JpZAoKIyBQbG90IHRoaXMKZ2ZfcG9pbnQoeX54LCBkYXRhID0gc2ltMSkgJT4lIAogIGdmX2xpbmUocHJlZH54LGRhdGEgPSBncmlkLGNvbG9yID0gInJlZCIsIHNpemUgPSAyKQoKYGBgCgpXZSBjYW4gYWRkIHJlc2lkdWFscyB0byB0aGUgb3JpZ2luYWwgZGF0YSBmcmFtZSB1c2luZyBgbW9kZWxyYAoKYGBge3IgcmVzaWR1YWxzfQpzaW0xIDwtIHNpbTEgJT4lIGFkZF9yZXNpZHVhbHMobW9kZWwgPSBzaW1fbW9kKQpzaW0xCgojIFBsb3R0aW5nIHRoZSByZXNpZHVhbHMKZ2ZfcG9pbnQoZGF0YSA9IHNpbTEsIHJlc2lkfngpICU+JSBnZl9obGluZSh5aW50ZXJjZXB0ID0gfiAwKQpnZl9kZW5zaXR5KGRhdGEgPSBzaW0xLCB+cmVzaWQpCgpgYGAKCkZyZXF1ZW5jeSBzcHJlYWQgb2YgdGhlIHJlc2lkdWFscyBsb29rcyByZWFzb25hYmxlLiBObyByZWd1bGFyIHBhdHRlcm4gImxlZnQgb3ZlciIgaW4gdGhlIHJlc2lkdWFscy4gCgoKIyBNb2RlbCBGYW1pbGllcwpUaGUgYG1vZGVsX21hdHJpeCgpYCBmdW5jdGlvbjoKPkl0IHRha2VzIGEgZGF0YSBmcmFtZSBhbmQgYSBmb3JtdWxhIGFuZCByZXR1cm5zIGEgdGliYmxlIHRoYXQgZGVmaW5lcyB0aGUgbW9kZWwgZXF1YXRpb246IGVhY2ggY29sdW1uIGluIHRoZSBvdXRwdXQgaXMgYXNzb2NpYXRlZCB3aXRoIG9uZSBjb2VmZmljaWVudCBpbiB0aGUgbW9kZWwsIHRoZSBmdW5jdGlvbiBpcyBhbHdheXMgYHkgPSBhXzEgKiBvdXQxICsgYV8yICogb3V0XzJgLgoKIyMgQ2F0ZWdvcmljYWwgVmFyaWFibGVzCgpXZSB1c2UgdGhlIGBzaW0yYCBkYXRhc2V0IHRvIGV4cGxvcmUgdGhpcy4gCmBgYHtyIENhdGVnb3JpY2FsIEluZGVwZW5kZW50IFZhcmlhYmxlc30KCnNpbTIgJT4lIGdmX3BvaW50KHl+eCkKCiMgTW9kZWwgZml0dGluZwptb2QyIDwtIGxtKHl+eCwgZGF0YSA9IHNpbTIpCgpncmlkIDwtIHNpbTIgJT4lIGRhdGFfZ3JpZCh4KSAlPiUgYWRkX3ByZWRpY3Rpb25zKG1vZDIpCmdyaWQKCiMgUGxvdCB0aGUgbW9kZWwgb24gdGhlIGRhdGEKZ2ZfcG9pbnQoeX54LCBkYXRhID0gc2ltMikgJT4lIAogIGdmX3BvaW50KHByZWR+eCxkYXRhID0gZ3JpZCwgY29sb3IgPSAicmVkIixzaXplID0gNCkKYGBgCldpdGggYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBmb3IgYHhgLCB3ZSBwcmVkaWN0IHRoZSBgbWVhbmAgdmFsdWUgZm9yIGVhY2ggY2F0ZWdvcnkgd2l0aCBvdXIgbW9kZWwuCgoKIyMgSW50ZXJhY3Rpb246IENvbnRpbnVvdXMgYW5kIENhdGVnb3JpY2FsIHZhcmlhYmxlCgpgYGB7ciBJbnRlcmFjdGlvbiBDb250aW51b3VzIGFuZCBDYXRlZ29yaWNhbCBWYXJpYWJsZXN9CnNpbTMKCmdmX3BvaW50KHl+eDEsIGRhdGEgPSBzaW0zLCBjb2xvciA9IH54MikKYGBgCgpXZSBjYW4gZml0IHR3byBraW5kcyBvZiBtb2RlbHM6IHdpdGggYW5kIHdpdGhvdXQgaW50ZXJhY3Rpb25zCgpgYGB7ciBUd28ga2luZHMgb2YgbW9kZWxzfQptb2QxID0gbG0oeSB+IHgxICsgeDIsIGRhdGEgPSBzaW0zKQptb2QyID0gbG0oeSB+IHgxICogeDIsIGRhdGEgPSBzaW0zKQoKIyBVc2UgZGF0YV9ncmlkCmdyaWQgPC0gc2ltMyAlPiUgZGF0YV9ncmlkKHgxLHgyKSAlPiUgZ2F0aGVyX3ByZWRpY3Rpb25zKG1vZDEsbW9kMikKZ3JpZApgYGAKCmBgYHtyIFZpc3VhbGlzaW5nIGJvdGggbW9kZWxzfQpzaW0zICU+JSAKICBnZl9wb2ludCh5IH4geDEsIGNvbG9yID0gfngyLCBkYXRhID0gc2ltMykgJT4lIAogIGdmX2xpbmUocHJlZCB+IHgxIHwgbW9kZWwsIGRhdGEgPSBncmlkKQoKYGBgCgpSZWNhbGwgdGhlIGRpc2N1c3Npb24gaW4gQ2hlc3RlciBJc21heSdzIFtgTW9kZXJuIERpdmVgXSh3d3cubW9kZXJuLmRpdmUuY29tKS4gYG1vZDFgIHVzZXMgdGhlIHNhbWUgYHNsb3BlYCBmb3IgYm90aCBtb2RlbHMgYW5kIG9ubHkgdmFyaWVzIHRoZSBgaW50ZXJjZXB0YCwgd2hlcmVhcyBgbW9kMmAgdmFyaWVzIGJvdGggbW9kZWwgcGFyYW1ldGVycy4gCgpXZSBjYW4gY2hlY2sgd2hpY2ggbW9kZWwgaXMgYmV0dGVyIGJ5IHBsb3R0aW5nIHJlc2lkdWFscy4gCmBgYHtyIFJlc2lkdWFsczogSW50ZXJhdGlvbiBvciBub3R9CnNpbTMgJT4lIGdhdGhlcl9yZXNpZHVhbHMobW9kMSwgbW9kMikgJT4lIAogIGdmX3BvaW50KHJlc2lkIH4geDF8bW9kZWwgfiB4MiwgY29sb3IgPSB+eDIsIGRhdGEgPSBzaW0zKQpgYGAKYG1vZDJgIHJlc2lkdWFscyBoYXZlIG5vIHBhdHRlcm4gYW5kIGxvb2sgcmFuZG9tLiBgbW9kMWAgcmVzaWR1YWxzIGRvIGhhdmUgcGF0dGVybnMsIGVzcGVjaWFsbHkgZm9yIGNhdGVnb3J5IGBiYC4KCiMjIEludGVyYWN0aW9uOiBUd28gQ29udGludW91cyBWYXJpYWJsZXMKCkFzIGluIHRoZSBsYXN0IHNlY3Rpb24gd2UgY2FuIGV4cGxvcmUgdHdvIGtpbmRzIG9mIG1vZGVscywgd2l0aCBhbmQgd3Rob3V0IGludGVyYWN0aW9uLgpgYGB7ciBDb250aW51b3VzIGluZGVwZW5kZW50IFZhcmlhYmxlcyB3aXRoIEludGVyYWN0aW9ufQpzaW00Cgptb2QxIDwtIGxtKCB5IH4geDEgKyB4MiwgZGF0YSA9IHNpbTQpCm1vZDIgPC0gbG0oIHkgfiB4MSAqIHgyLCBkYXRhID0gc2ltNCkKCiMgVmlzdWFsaXNhdGlvbgpncmlkIDwtIHNpbTQgJT4lIAogIGRhdGFfZ3JpZCh4MSA9IHNlcV9yYW5nZSh4MSxuID0gNSxwcmV0dHkgPSBUUlVFKSwgCiAgICAgICAgICAgIHgyID0gc2VxX3JhbmdlKHgyLCA1LHByZXR0eSA9IFRSVUUpKSAlPiUgCiAgZ2F0aGVyX3ByZWRpY3Rpb25zKG1vZDEsIG1vZDIpCiMgc2VxX3JhbmdlIGlzIGEgbW9kZWxyIGNvbW1hbmQsIHRvIGdlbmVyYXRlIG4gbnVtYmVycyBzcGFjZWQgb3ZlciB0aGUgcmFuZ2Ugb2YgYSB2YXJpYWJsZS4gCmdyaWQKCiMgVmlzdWFsaXNhdGlvbgpnZl90aWxlKHgyIH4geDEgfCBtb2RlbCwgZmlsbCA9IH4gcHJlZCwgZGF0YSA9IGdyaWQgKQpgYGAKCkNhbid0IHNlZSBtdWNoIGRpZmZlcmVuY2UgdGhlcmUuLi4uCgpgYGB7ciBEaWZmZXJlbnQgd2F5fQpnZl9saW5lKHByZWQgfiB4MSB8IG1vZGVsLCBjb2xvciA9ICB+IHgyLCBncm91cCA9IH4geDIsIGRhdGEgPSBncmlkKSAlPiUgZ2ZfcmVmaW5lKHNjYWxlX2NvbG9yX2Rpc3RpbGxlcihwYWxldHRlID0gNykpCgpnZl9saW5lKHByZWQgfiB4MiB8fiBtb2RlbCwgY29sb3IgPSB+IHgxLCBncm91cCA9IH4geDEsIGRhdGEgPSBncmlkKQpgYGAKCkxldCdzIGxvb2sgYXQgdGhlIHJlc2lkdWFscy4uLgpgYGB7ciBSZXNpZHVhbHMgZm9yIENvbnRpbnVvdXMgaW50ZXJhY3RpbmcgdmFyaWFibGVzfQpzaW00ICU+JSAKICBnYXRoZXJfcmVzaWR1YWxzKG1vZDEsIG1vZDIpICU+JSAKICBnZl9wb2ludChyZXNpZCB+IHgxfG1vZGVsIH4geDIsIGNvbG9yID0gfngyLCBkYXRhID0gc2ltNCkKCiMgV2hhdCBwbG90IGNhbiB3ZSB1c2UgdG8gc2hvdyB3aGljaCBtb2RlbCBpcyBiZXR0ZXI/CnNpbTQgJT4lIAogIGdhdGhlcl9yZXNpZHVhbHMobW9kMSwgbW9kMikgJT4lIAogIGdmX3BvaW50KG1lYW4oYWJzKHJlc2lkKSl+IHgxIHwgbW9kZWwgfiB4MiwgY29sb3IgPSB+IHgyLCBkYXRhID0gLikKIyBORUVEUyBNT1JFIElNQUdJTkFUSU9OIEFORCBNT1JFIFdPUkshIQogIApgYGAKCiMjIFRyYW5zZm9ybWF0aW9uIHdoaWxlIE1vZGVsbGluZwoKRGF0YSB2YXJpYWJsZXMgY2FuIGFsc28gYmUgYWxnZWJyYWljYWxseSB0cmFuc2Zvcm1lZCB3aGlsZSBwdXR0aW5nIHRoZW0gaW50byB0aGUgbW9kZWwuIEFjdHVhbCBhcml0aG1ldGljIG9wZXJhdG9ycyBsaWtlIGArYCBhbmQgYCpgIHNob3VsZCBiZSB3cmFwcGVkIGluIGBJKClgIHRvIGVuc3VyZSB0aGF0IHRoZXkgYXJlIGludGVycHJldGVkIGNvcnJlY3RseS4gSXQgaXMgYWx3YXlzIGdvb2QgdG8gY2hlY2sgd2l0aCBgbW9kZWxfbWF0cml4YCB3aGF0IHRoZSBtb2RlbCBpcyBkb2luZywgc28gdGhhdCB3ZSBrbm93IHdoYXQgd2UgYXJlIGdldHRpbmcuIAo+IE5vdGUgd2UgY2FuIGRvIHRoZSBtb2RlbGxpbmcgaXRzZWxmIGluc2lkZSB0aGUgYCBtb2RlbF9tYXRyaXhgIGNvbW1hbmQhCgpgYGB7ciBUcmFuc2Zvcm1hdGlvbnMgaW4gTW9kZWxzfQpkZiA8LSB0cmliYmxlKH55LCB+eCwgMSwxLDIsMiwzLDMpCgptb2RlbF9tYXRyaXgoZGYsIHkgfiB4XjIgKyB4KQojIFRoaXMgdXNlcyB0aGUgV2lsa2luc29uLVJvZ2VycyBOb3RhdGlvbiAhIQoKbW9kZWxfbWF0cml4KGRmLCB5IH4gSSh4XjIpICsgeCkKIyBDYW4gYWxzbyB1c2UgSSh4XjIgKyB4KQpgYGAKClRoZXJlIGFyZSBtYW55IHRyYW5zZm9ybWF0aW9ucyBwb3NzaWJsZSBVc2luZyBUYXlsb3IncyBzZXJpZXMgaXMgb25lIHdheQpgYGB7ciBUYXlsb3Igc2VyaWVzfQptb2RlbF9tYXRyaXgoZGYsIHkgfiBwb2x5KHgsIDIpKQpgYGAKCmBwb2x5YCBmaXRzIHRoZSBkYXRhIHdlbGwgd2l0aGluIHRoZSByYW5nZTsgb3V0c2lkZSwgd2hlbiBpdCBpcyBleHRyYXBvbGF0aW5nLCBpdCBtYXkgbWF5IHNob290IG9mZiB0byBpbmZpbml0eS4gCkluIHRoaXMgY2FzZSBpdCBpcyBiZXR0ZXIgdG8gdXNlIGBuYXR1cmFsIHNwbGluZXNgLCB3aGljaCBpcyBzb21ld2hhdCBiZXR0ZXIsIHRob3VnaCBpdCBzdGlsbCBtYWtlcyBlcnJvcnMgb3V0c2lkZSB0aGUgZGF0YSByYW5nZS4gCgpgYGB7ciBTcGxpbmVzfQpsaWJyYXJ5KHNwbGluZXMpCm1vZGVsX21hdHJpeChkZiwgeSB+IG5zKHgsIDIpKQpgYGAKCkxldCB1cyBtb2RlbCBgbm9ubGluZWFyIGRhdGFgLiAKCmBgYHtyIE1vZGVsbGluZyBub25saW5lYXIgZGF0YX0Kc2ltNSA8LSB0aWJibGUoeCA9IHNlcSgwLCAzLjUgKiBwaSwgbGVuZ3RoID0gNTApLAogICAgICAgICAgICAgICB5ID0gNCAqIHNpbih4KSArIHJub3JtKGxlbmd0aCh4KSkpCmdmX3BvaW50KHkgfiB4LCBkYXRhID0gc2ltNSkKCiMgV2UgY2FuIGZpdCBtdWx0aXBsZSBtb2RlbHMgdG8gdGhpcyBkYXRhCm1vZDEgPC0gbG0oeSB+IG5zKHgsIDEpLCBkYXRhID0gc2ltNSkKbW9kMiA8LSBsbSh5IH4gbnMoeCwgMiksIGRhdGEgPSBzaW01KQptb2QzIDwtIGxtKHkgfiBucyh4LCAzKSwgZGF0YSA9IHNpbTUpCm1vZDQgPC0gbG0oeSB+IG5zKHgsIDQpLCBkYXRhID0gc2ltNSkKbW9kNSA8LSBsbSh5IH4gbnMoeCwgNSksIGRhdGEgPSBzaW01KQoKZ3JpZCA8LSBzaW01ICU+JSAKICBkYXRhX2dyaWQoeCA9IHNlcV9yYW5nZSh4LG4gPSA1MCxleHBhbmQgPSAwLjEpKSAlPiUgCiAgZ2F0aGVyX3ByZWRpY3Rpb25zKG1vZDEsbW9kMixtb2QzLG1vZDQsbW9kNSwgLnByZWQgPSAieSIpCgojIFBsb3R0aW5nIHRoZSA1IG1vZGVscwogZ2ZfcG9pbnQoZGF0YSA9IHNpbTUsIHkgfiB4KSAlPiUgCiAgIGdmX2xpbmUoZGF0YSA9IGdyaWQsIHkgfiB4IHwgbW9kZWwsIGNvbG9yID0gfm1vZGVsKSAKIApgYGAKPiBOb3RpY2UgdGhhdCB0aGUgZXh0cmFwb2xhdGlvbiBvdXRzaWRlIHRoZSByYW5nZSBvZiB0aGUgZGF0YSBpcyBjbGVhcmx5IGJhZC4gVGhpcyBpcyB0aGUgZG93bnNpZGUgdG8gYXBwcm94aW1hdGluZyBhIGZ1bmN0aW9uIHdpdGggYSBwb2x5bm9taWFsLiBCdXQgdGhpcyBpcyBhIHZlcnkgcmVhbCBwcm9ibGVtIHdpdGggZXZlcnkgbW9kZWw6IHRoZSBtb2RlbCBjYW4gbmV2ZXIgdGVsbCB5b3UgaWYgdGhlIGJlaGF2aW91ciBpcyB0cnVlIHdoZW4geW91IHN0YXJ0IGV4dHJhcG9sYXRpbmcgb3V0c2lkZSB0aGUgcmFuZ2Ugb2YgdGhlIGRhdGEgdGhhdCB5b3UgaGF2ZSBzZWVuLiBZb3UgbXVzdCByZWx5IG9uIHRoZW9yeSBhbmQgc2NpZW5jZS4KCgojIyBPdGhlciBNb2RlbCBGYW1pbGllcwoKMS4gKiogR2VuZXJhbGlzZWQgTGluZWFyIG1vZGVsczoqKiBgc3RhdHM6OmdsbSgpYCBleHRlbmQgbGluZWFyIG1vZGVscyB0byBgY291bnRgIG9yIGBiaW5hcnlgIHJlc3BvbnNlIHZhcmlhYmxlcy4gVXNlIGEgZGlmZmVyZW50IG1ldHJpYyBmb3IgZGlzdGFuY2UuCgoyLiAqKiBHZW5lcmFsaXNlZCBBZGRpdGl2ZSBNb2RlbHM6KiogYG1nY3Y6OmdhbSgpYCBjYW4gdXNlIGFyYml0cmFyeSBzbW9vdGggbW9kZWxsaW5nIGZ1bmN0aW9ucyBsaWtlIGAgeSB+IHMoeClgLiBgZ2FtKClgIHdpbGwgZXN0aW1hdGUgdGhlIGZ1bmN0aW9uLiAoUmF0aGVyIGxpa2UgdGhlIHJlZ3Jlc3Npb24gc29mdHdhcmUgSSB1c2VkIGVhcmxpZXIuKSAKCjMuICoqIFBlbmFsaXNlZCBMaW5lYXIgTW9kZWxzOioqIGBnbG1uZXQ6OmdsbW5ldCgpYCBBZGRzIGEgcGVuYWx0eSB2ZWN0b3IgaW4gYHBhcmFtZXRlciBzcGFjZWAgY29ycmVzcG9uZGluZyB0byBkaXN0YW5jZSBvZiB0aGF0IHZlY3RvciBmcm9tIHRoZSBvcmlnaW4uIFRlbmRzIHRvIG1ha2UgbW9kZWxzIGdlbmVyYWxpc2UgYmV0dGVyIHRvIG5ldyBkYXRhc2V0cyBmcm9tIHRoZSBgcG9wdWxhdGlvbmAuIAoKNC4gKiogUm9idXN0IExpbmVhciBNb2RlbHM6KiogYE1BU1M6OnJsbSgpYCB0d2Vha3MgdGhlIGRpc3RhbmNlIG1ldHJpYyB0byBkb3duLXdlaWdodCBkYXRhIHBvaW50cyB0aGF0IGFyZSBmYXIgYXdheSwgaS5lLiBvdXRsaWVycy4gTGVzcyBzZW5zaXRpdmUgdG8gb3V0bGllcnMsIGJ1dCBub3Qgc28gZ29vZCB3aGVuIHRoZXJlIGFyZSAqbm8gb3V0bGllcnMqLgoKNS4gKiogVHJlZXM6KiogRml0IHBpZWNlLXdpc2UgbGluZWFyIG1vZGVscyB0byBzbWFsbGVyIGFuZCBzbWFsbGVyIHBpZWNlcyBvZiBkYXRhICggbGlrZSBhICoqTGluZGVubWF5ZXIgZnJhY3RhbCoqKS4gV29yayBiZXN0IHdoZW4gdXNlZCBpbiBhZ2dyZWdhdGUgbW9kZWxzIGxpa2UgYHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KClgIGFuZCAqKmdyYWRpZW50IGJvb3N0KiogYHhnYm9vc3Q6OnhnYm9vc3QoKWAuCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCkEgR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVsIChHTE0vR0xaKSBoZWxwcyByZXByZXNlbnQgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSBhcyBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiBpbmRlcGVuZGVudCB2YXJpYWJsZXMuCiBJbiBpdHMgc2ltcGxlc3QgZm9ybSwgYSBsaW5lYXIgbW9kZWwgc3BlY2lmaWVzIHRoZSAobGluZWFyKSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhIGRlcGVuZGVudCAob3IgcmVzcG9uc2UpIHZhcmlhYmxlIFksIGFuZCBhIHNldCBvZiBwcmVkaWN0b3IgdmFyaWFibGVzLCB0aGUgWCdzLCBzbyB0aGF0CgokJApZID0gYl8wICsgYl8xWF8xICsgYl8yWF8yICsgLi4uICsgYl9rWF9rICArIGUKJCQgCgpJbiB0aGUgR0xacywgdGhlIG1vZGVsIGlzIGFzc3VtZWQgdG8gYmU6CiQkClkgPSBnIChiXzAgKyBiXzFYXzEgKyBiXzJYXzIgKyAuLi4gKyBiX2tYX2sgKSsgZQokJApUaGUgYGludmVyc2VgIG9mIHRoZSBmdW50aW9uIGBnKC4uLilgLCBzYXkgZiguLi4pYCBpcyBjYWxsZWQgdGhlIGBsaW5rIGZ1bmN0aW9uYCwgc28gdGhhdDoKCiQkCmYoXG11X1kpPSBiXzAgKyBiXzFYXzEgKyBiXzJYXzIgKyAuLi4gKyBiX2tYX2sKJCQKCgpHTFpzIHdvcmsgd2hlbiA6CgphKSB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIGhhcyBhIGRpc2NyZXRlL211bHRpbm9taWFsIGRpc3RyaWJ1dGlvbi4gVGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZGVwZW5kZW50IG9yIHJlc3BvbnNlIHZhcmlhYmxlIGNhbiBiZSAoZXhwbGljaXRseSkgbm9uLW5vcm1hbCwgYW5kIGRvZXMgbm90IGhhdmUgdG8gYmUgY29udGludW91cywgaS5lLiwgaXQgY2FuIGJlIGJpbm9taWFsLCBtdWx0aW5vbWlhbCwgb3Igb3JkaW5hbCBtdWx0aW5vbWlhbCAoaS5lLiwgY29udGFpbiBpbmZvcm1hdGlvbiBvbiByYW5rcyBvbmx5KTsgCgpiKSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gICBkZXBlbmRlbnQgYW5kIGluZGVwZW5kZW50IHZhcmlhYmxlIChpLmUuIHRoZSBgbGluayBmdW5jdGlvbmApIGlzIGluaGVyZW50bHkgbm9ubGluZWFyLCBvciBhIHBvd2VyIHJlbGF0aW9uc2hpcCwgZm9yIGV4YW1wbGUuIAoKIyMgVHlwZXMgb2YgYGxpbmsgZnVuY3Rpb25zYCBhbmQgZGlzdHJpYnV0aW9ucyBvZiBgeWAgZGVwZW5kZW50IHZhcmlhYmxlcwoKVmFyaW91cyBsaW5rIGZ1bmN0aW9ucyBjYW4gYmUgY2hvc2VuIGJhc2VkIG9uIHRoZSBhc3N1bWJuZWQgZGlzdHJpYnV0aW9ucyBvZiB0aGUgeSBkZXBlbmRlbnQgdmFyaWFibGU6CgpgYGB7ciBgbGlua2AgZnVuY3Rpb24gdHlwZXMsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTZ9CnBsb3QoCnBsYW50dW1sKCIKICBAc3RhcnRtaW5kbWFwCisgYGxpbmtgIGZ1bmN0aW9ucworKyBOb3JtYWwsIEdhbW1hLCBJbnZlcnNlIE5vcm1hbCwgUG9pc3NvbgorKysgSWRlbnRpdHkKKysrKyBgZih6KSA9IHpgCisrKyBMb2cgCisrKysgZih6KSA9IGxvZyh6KQorKysgUG93ZXIgZih6KSA9IHpeYQorKyBCaW5vbWlhbCwgT3JkaW5hbCBNdWx0aW5vbWlhbAorKysgTG9naXQgYGYoeikgPSBsb2coei8oMS16KSkKKysrIFByb2JpdCBgZih6KSA9IGludm5vcm0oeikKKysrIENvbXBsZW1lbnRhcnkgbG9nLWxvZyBgZih6KSA9IGxvZygtbG9nKDEteikpCisrKyBMb2ctbG9nIGBmKHopID0gbG9nKC1sb2coeikpCi0tIE11bHRpbm9taWFsCi0tLSBHZW5lcmFsaXplZCBsb2dpdCAKLS0tLSBmKHoxfHoyLi4uemMpID0gbG9nKHgxLygxLXoxLS4uLi4uLXpjKSkgd2hlcmUgbW9kZWwgaGFzIGBjKzFgIGNhdGVnb3JpZXMKQGVuZG1pbmRtYXAiKSkKYGBgCiMgQ29jYUNvbGEgU2FsZXMgRGF0YSBFeHBsb3JhdGlvbgoKYGBge3IgRURBfQojIFRlbXBlcmF0dXJlIHZzIENvY2FDb2xhIHNhbGVzCmNvbGEgPC0gcmVhZF9jc3YoIi4vY29sYS5jc3YiKQpwZW5hbHR5IDwtIHJlYWRfY3N2KCIuL3BlbmFsdHkuY3N2IikKCnN0cihjb2xhKQpnZl9wb2ludChDb2xhIH4gVGVtcGVyYXR1cmUsIGRhdGEgPSBjb2xhKQpgYGAKCiMjIExpbmVhciBNb2RlbCBGaXR0aW5nCmBgYHtyIExpbmVhciBtb2RlbH0KCm1vZGVsID1sbShkYXRhID0gY29sYSwgQ29sYSB+IFRlbXBlcmF0dXJlKQpnZl9wb2ludChDb2xhIH4gVGVtcGVyYXR1cmUsIGRhdGEgPSBjb2xhKSAlPiUgCiAgZ2ZfYWJsaW5lKGludGVyY2VwdCA9IH5tb2RlbCRjb2VmZmljaWVudHNbMV0sCiAgICAgICAgICAgIHNsb3BlID0gfm1vZGVsJGNvZWZmaWNpZW50c1syXSxkYXRhID0gY29sYSkKCiNDYWxjdWxhdGUgUk1TRQpQcmVkQ29sYSA8LSBwcmVkaWN0KG1vZGVsLCBjb2xhKQpSTVNFIDwtIG1vZGVscjo6cm1zZShtb2RlbCwgY29sYSkKIyMKbW9kZWwKUk1TRQpgYGAKClRoZSBsaW5lYXIgYG1vZGVsYCBpcyBjbGVhcmx5IGluYWRlcXVhdGUgYW5kIG1ha2VzIGZhdWx0eSBwcmVkaWN0aW9ucy4gV2UgdHJ5IHRvIHVzZSBhIGBsb2ctbGluZWFyYCBtb2RlbCBuZXh0LCBzaW5jZSB0aGUgYENvbGFgIHNhbGVzIGZpZ3VyZSBzZWVtcyB0byBoYXZlIGFuIGV4cG9uZW50aWFsIHJlbGF0aW9uc2hpcCB3aXRoIGBUZW1wZXJhdHVyZWAuIFdlIHNlZSB0aGF0OgokJENvbGEgPSBhICogYiBeIHtUZW1wZXJhdHVyZX0gLi4uLkVxbigxKSQkCgo+IFN1Y2ggZ3Jvd3RoIG1vZGVscyBkZXBpY3QgYSB2YXJpZXR5IG9mIHJlYWwgbGlmZSBzaXR1YXRpb25zIGFuZCBjYW4gYmUgbW9kZWxlZCB1c2luZyBsb2ctbGluZWFyIHJlZ3Jlc3Npb24uICBBcGFydCBmcm9tIGV4cG9uZW50aWFsIHJlbGF0aW9uc2hpcCwgbG9nIHRyYW5zZm9ybWF0aW9uIG9uICh0aGUpIGRlcGVuZGVudCB2YXJpYWJsZSBpcyBhbHNvIHVzZWQgd2hlbiBkZXBlbmRlbnQgdmFyaWFibGUgZm9sbG93czogCmEpIGxvZy1ub3JtYWwgZGlzdHJpYnV0aW9uIC0gbG9nLW5vcm1hbCBkaXN0cmlidXRpb24gaXMgZGlzdHJpYnV0aW9uIG9mIGEgcmFuZG9tIHZhcmlhYmxlIHdob3NlIGxvZyBmb2xsb3dzIG5vcm1hbCBkaXN0cmlidXRpb24uIFRodXMsIHRha2luZyBsb2cgb2YgYSBsb2ctbm9ybWFsIHJhbmRvbSB2YXJpYWJsZSBtYWtlcyB0aGUgdmFyaWFibGUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgYW5kIGZpdCBmb3IgbGluZWFyIHJlZ3Jlc3Npb24uICAKYikgUG9pc3NvbiBkaXN0cmlidXRpb24gLSBQb2lzc29uIGRpc3RyaWJ1dGlvbiBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHJhbmRvbSB2YXJpYWJsZSB0aGF0IHJlc3VsdHMgZnJvbSBhIFBvaXNzb24gZXhwZXJpbWVudC4gRm9yIGV4YW1wbGUsIHRoZSBudW1iZXIgb2Ygc3VjY2Vzc2VzIG9yIGZhaWx1cmVzIGluIGEgdGltZSBwZXJpb2QgVCBmb2xsb3dzIFBvaXNzb24gZGlzdHJpYnV0aW9uLiAgCgpUYWtpbmcgYGxvZ2Agb24gYm90aCBzaWRlcyBvZiBFcW4uMToKCiQkbG9nKENvbGEpID0gbG9nKGEpICsgVGVtcGVyYXR1cmUgKiBsb2coYikkJAoKIyMgTG9nIE1vZGVsIEZpdHRpbmcKV2UgdHJhbnNmb3JtIHRoZSBkYXRhIHVzaW5nIGxvZyBhbmQgdGhlbiBmaXQgYSBsaW5lYXIgbW9kZWwgdG8gdGhlIGBsb2ctdHJhbnNmb3JtZWRgIHZhcmlhYmxlcwoKYGBge3IgTG9nLUxpbmVhciBNb2RlbH0KY29sYSA8LSBjb2xhICU+JSBtdXRhdGUobG9nQ29sYSA9IGxvZyhDb2xhKSkKCmxvZ21vZGVsIDwtIGxtKGxvZ0NvbGEgfiBUZW1wZXJhdHVyZSwgZGF0YSA9IGNvbGEpCmxvZ21vZGVsCgojIFBsb3RzCmdmX3BvaW50KGRhdGEgPSBjb2xhLCBsb2dDb2xhflRlbXBlcmF0dXJlKSAlPiUgCiAgZ2ZfYWJsaW5lKGludGVyY2VwdCA9IH5sb2dtb2RlbCRjb2VmZmljaWVudHNbMV0sc2xvcGUgPSB+bG9nbW9kZWwkY29lZmZpY2llbnRzWzJdKQoKIyBQcmVkaWN0aW9ucyBhbmQgUk1TRQpQcmVkTG9nQ29sYSA8LSBwcmVkaWN0KGxvZ21vZGVsLGNvbGEpClJNU0Vsb2cgPC0gbW9kZWxyOjpybXNlKGxvZ21vZGVsLCBjb2xhKQoKUHJlZExvZ0NvbGEKUk1TRWxvZwoKYGBgCkhlbmNlIHRoZSBsb2ctbGluZWFyIG1vZGVsIGlzOgoKJCQgbG9nKENvbGFfaSkgPSAtMC45MDkgKyAwLjE3MiAqIGxvZyhUZW1wZXJhdHVyZV9pKSAkJAoKYGBge3J9CnN0cihQcmVkTG9nQ29sYSkKZ2ZfY29sKGRhdGEgPSBQcmVkTG9nQ29sYSx2YWx1ZSB+IHJvd19udW1iZXIoUHJlZExvZ0NvbGEpKQpgYGAKCgoKCg==